import os
import re
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from pathlib import Path
import threading
import queue
from typing import List, Dict, Any, Optional
from datetime import date
import pandas as pd  # 仅用于示例

class FunctionAnalyzer:
    """函数分析器，用于查找未使用的函数"""
    
    def __init__(self):
        self.functions = {}  # 存储所有函数的信息
        self.unused_functions = []  # 存储未使用的函数
        self.stop_requested = False  # 停止请求标志
        self._log = lambda msg: print(msg)  # 默认日志输出到控制台
        self.debug_mode = True  # 保持调试模式开启
    
    def _find_references_in_files(self, files, search_term):
        """在多个文件中查找引用"""
        references = {}
        file_count = 0
        match_count = 0
        
        self._log(f"[文件扫描] 开始查找函数 '{search_term}' 的引用")
        
        for file_path in files:
            if self.stop_requested:
                return {}
                
            file_count += 1
            try:
                file_name = os.path.basename(file_path)
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.readlines()
                    file_matches = 0
                    for line_num, line in enumerate(content, 1):
                        if search_term in line:
                            if file_name not in references:
                                references[file_name] = []
                            
                            # 获取上下文（行号和包含搜索词的行）
                            references[file_name].append({
                                "line": line_num,
                                "context": line.strip(),
                                "code": content,
                                "file_path": file_path
                            })
                            file_matches += 1
                            match_count += 1
                    
                    if file_matches > 0:
                        self._log(f"[文件扫描] 在文件 {file_name} 中找到 {file_matches} 个 '{search_term}' 的匹配")
            except Exception as e:
                self._log(f"[警告] 无法读取文件 {file_path}: {str(e)}")
        
        self._log(f"[文件扫描] 完成查找函数 '{search_term}' 的引用，共找到 {match_count} 个匹配")
        return references
    
    def _extract_functions_from_files(self, files):
        """从文件中提取所有函数定义，支持更复杂的返回类型"""
        # 最终版本的函数定义正则表达式，支持：
        # - 类方法（self参数）
        # - 带点号的返回类型（如pd.DataFrame）
        # - 完整的参数列表（包括类型注解、默认值等）
        function_pattern = re.compile(
            r'^\s*(?:@[^\n]+\n\s*)*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\('
            r'(?:\s*self(?:\s*,\s*|$)|.*?)?'  \
            r'[^\)]*?\s*\)(?:\s*->\s*[.\w]+)?\s*:'  # 支持带点号的返回类型
        )
        
        self._log(f"[函数分析] 开始提取函数定义，共 {len(files)} 个文件")
        
        for file_path in files:
            if self.stop_requested:
                return
                
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.readlines()
                    
                    for line_num, line in enumerate(content, 1):
                        match = function_pattern.match(line)
                        if match:
                            function_name = match.group(1)
                            # 存储函数信息
                            self.functions[function_name] = {
                                'file_path': file_path,
                                'line_num': line_num,
                                'definition': line.strip()
                            }
                        elif self.debug_mode:
                            # 调试模式：记录未匹配的函数定义行
                            if line.strip().startswith('def '):
                                self._log(f"[调试] 未匹配的函数定义行: {line.strip()}")
            except Exception as e:
                self._log(f"[警告] 无法分析文件 {file_path}: {str(e)}")
    
    def find_unused_functions(self, files):
        """查找定义了但未使用的函数"""
        self.functions.clear()
        self.unused_functions.clear()
        self.stop_requested = False
        
        # 先提取所有函数定义
        self._extract_functions_from_files(files)
        
        if self.stop_requested:
            return []
        
        self._log(f"[函数分析] 提取完成，找到 {len(self.functions)} 个函数定义")
        
        # 结果列表
        self.unused_functions = []
        
        self._log(f"[函数分析] 开始分析函数引用...")
        
        # 遍历每个函数，查找其引用
        for function_name, func_info in self.functions.items():
            if self.stop_requested:
                return []
                
            # 查找函数引用
            self._log(f"[函数分析] 开始分析函数 '{function_name}' 的引用...")
            references = self._find_references_in_files(files, function_name)
            
            # 计算匹配总数（排除函数定义行本身）
            total_matches = 0
            for file_data in references.values() or []:
                for ref in file_data:
                    # 跳过函数定义行
                    if ref['file_path'] == func_info['file_path'] and \
                       ref['line'] == func_info['line_num']:
                        continue
                    total_matches += 1
            
            # 只要匹配数≥1，就认为函数被使用
            if total_matches >= 1:
                self._log(f"[函数分析] 函数 '{function_name}' 被引用 {total_matches} 次，判定为已使用")
            else:
                self._log(f"[函数分析] 函数 '{function_name}' 未被引用，判定为未使用")
                self.unused_functions.append({
                    'name': function_name,
                    'file_path': func_info['file_path'],
                    'line_num': func_info['line_num'],
                    'definition': func_info['definition']
                })
        
        self._log(f"[函数分析] 完成分析，发现 {len(self.unused_functions)} 个未使用的函数")
        return self.unused_functions
    
    def stop_analysis(self):
        """请求停止分析过程"""
        self.stop_requested = True


class UnusedFunctionFinderGUI:
    """未使用函数查找工具的图形界面"""
    
    def __init__(self, root):
        self.root = root
        self.root.title("未使用函数查找工具")
        self.root.geometry("900x600")
        
        self.analyzer = FunctionAnalyzer()
        self.file_paths = []
        self.analysis_thread = None
        self.log_queue = queue.Queue()
        
        self.create_widgets()
        self.setup_logger()
        
        # 启动日志处理线程
        self.root.after(100, self.process_log_queue)
    
    def create_widgets(self):
        """创建GUI组件"""
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 文件选择区域
        file_frame = ttk.LabelFrame(main_frame, text="文件选择", padding="10")
        file_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(file_frame, text="文件/目录:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        
        self.file_entry = ttk.Entry(file_frame, width=60)
        self.file_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        
        ttk.Button(file_frame, text="浏览文件", command=self.browse_files).grid(row=0, column=2, padx=5, pady=5)
        ttk.Button(file_frame, text="浏览目录", command=self.browse_directory).grid(row=0, column=3, padx=5, pady=5)
        
        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="日志", padding="10")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        self.log_text = tk.Text(log_frame, wrap=tk.WORD, height=10)
        self.log_text.pack(fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(self.log_text, command=self.log_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.config(yscrollcommand=scrollbar.set)
        self.log_text.config(state=tk.DISABLED)
        
        # 结果区域
        result_frame = ttk.LabelFrame(main_frame, text="未使用的函数", padding="10")
        result_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        columns = ("函数名", "文件路径", "行号", "函数定义")
        self.result_tree = ttk.Treeview(result_frame, columns=columns, show="headings")
        
        for col in columns:
            self.result_tree.heading(col, text=col)
        
        self.result_tree.column("函数名", width=120)
        self.result_tree.column("文件路径", width=300)
        self.result_tree.column("行号", width=60, anchor=tk.CENTER)
        self.result_tree.column("函数定义", width=300)
        
        self.result_tree.pack(fill=tk.BOTH, expand=True)
        
        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=10)
        
        self.start_button = ttk.Button(button_frame, text="开始分析", command=self.start_analysis)
        self.start_button.pack(side=tk.LEFT, padx=5)
        
        self.stop_button = ttk.Button(button_frame, text="停止分析", command=self.stop_analysis, state=tk.DISABLED)
        self.stop_button.pack(side=tk.LEFT, padx=5)
        
        self.clear_button = ttk.Button(button_frame, text="清空结果", command=self.clear_results)
        self.clear_button.pack(side=tk.LEFT, padx=5)
    
    def setup_logger(self):
        """设置日志记录功能"""
        def logger(message):
            self.log_queue.put(message)
        
        self.analyzer._log = logger
    
    def process_log_queue(self):
        """处理日志队列"""
        while not self.log_queue.empty():
            try:
                message = self.log_queue.get_nowait()
                self.log_text.config(state=tk.NORMAL)
                self.log_text.insert(tk.END, message + "\n")
                self.log_text.see(tk.END)
                self.log_text.config(state=tk.DISABLED)
            except queue.Empty:
                pass
        self.root.after(100, self.process_log_queue)
    
    def browse_files(self):
        """浏览并选择文件"""
        files = filedialog.askopenfilenames(
            title="选择Python文件",
            filetypes=[("Python文件", "*.py"), ("所有文件", "*.*")]
        )
        
        if files:
            self.file_paths = list(files)
            self.file_entry.delete(0, tk.END)
            self.file_entry.insert(0, f"已选择 {len(files)} 个文件")
    
    def browse_directory(self):
        """浏览并选择目录"""
        directory = filedialog.askdirectory(title="选择目录")
        
        if directory:
            # 获取目录下所有Python文件
            self.file_paths = []
            for root, _, files in os.walk(directory):
                for file in files:
                    if file.endswith(".py"):
                        self.file_paths.append(os.path.join(root, file))
            
            self.file_entry.delete(0, tk.END)
            self.file_entry.insert(0, f"已选择 {len(self.file_paths)} 个文件")
    
    def start_analysis(self):
        """开始分析未使用的函数"""
        if not self.file_paths:
            messagebox.showwarning("警告", "请先选择文件或目录")
            return
        
        # 清空之前的结果
        self.clear_results()
        
        # 更新UI状态
        self.status_var.set("分析中...")
        self.start_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        
        # 在单独的线程中执行分析，避免阻塞UI
        self.analysis_thread = threading.Thread(target=self._run_analysis)
        self.analysis_thread.daemon = True
        self.analysis_thread.start()
    
    def _run_analysis(self):
        """在线程中运行分析任务"""
        try:
            unused_functions = self.analyzer.find_unused_functions(self.file_paths)
            
            # 在主线程中更新结果
            self.root.after(0, self._update_results, unused_functions)
        except Exception as e:
            self.root.after(0, self._show_error, str(e))
        finally:
            # 更新UI状态
            self.root.after(0, self._analysis_complete)
    
    def _update_results(self, unused_functions):
        """更新结果列表"""
        for func in unused_functions:
            self.result_tree.insert("", tk.END, values=(
                func['name'],
                func['file_path'],
                func['line_num'],
                func['definition']
            ))
    
    def _show_error(self, message):
        """显示错误消息"""
        messagebox.showerror("错误", message)
    
    def _analysis_complete(self):
        """分析完成后的UI更新"""
        self.status_var.set(f"分析完成，找到 {len(self.analyzer.unused_functions)} 个未使用的函数")
        self.start_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)
    
    def stop_analysis(self):
        """停止分析过程"""
        if self.analysis_thread and self.analysis_thread.is_alive():
            self.analyzer.stop_analysis()
            self.status_var.set("正在停止分析...")
    
    def clear_results(self):
        """清空结果列表和日志"""
        for item in self.result_tree.get_children():
            self.result_tree.delete(item)
        
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)
        
        self.status_var.set("就绪")


if __name__ == "__main__":
    root = tk.Tk()
    app = UnusedFunctionFinderGUI(root)
    root.mainloop()    